WebScrape the Airport codes
Let’s see what it looks like in action
html.scrape <- read_html("http://www.leonardsguide.com/us-airport-codes.shtml")
Now the tricky part is seeing which nodes contain our data. Here it would be wise use chrome dev tools to see which node contains the info. Here it looks like the
node contains the data.
codes <- html.scrape %>% html_nodes("td") %>% html_text()
Let’s take a moment to recap:
- We read in the entire html code contained in the site
- We found the node that contains our information
- We got the data with html_nodes()
- We converted into text with html_text()
But it is still not in a format we would like. It should be a dataframe with state, city, and airport code as columns instead of a long vector. Now comes the power of the tidyverse.
codes <- as.data.frame(codes)
show(head(codes))
Looking at the above, at first glance it would seem difficult to get this into the data frame we want. We also can see one observation that we need to fix…
You can see that some airports have the word “Airport” attached to their name, while others do not. This makes it hard to distinguish which is an airport and what is not. We know that any 3 letter airport code must have a preceding airport in the element above it. Let’s write code to write the word “Airport” to any element that needs it.
Example:
We see that MOB’s airport name is “Mobile” and should be “Mobile Airport”
#loop through dataframe
for(i in 1:nrow(codes)){
#find 3 letter airport codes
if(nchar(codes[[i,1]]) == 3){
#check if the string above it contains the word "Airport"
if(!str_detect(codes[[i-1,1]], "Airport")){
#if it doesn't then write the word "Airport" at the end
codes[[i-1, 1]] <- paste(codes[[i-1,1]], "Airport", sep = " ")
}
}
}
Now we can take full advantage of R’s separate function.
#use R's separate function as we need to separate 3 times
#first regex splits anything thats 2 letters or 3 letters or contains "Airport"
#now you see why we needed to add the word Airport to a few elements
regexp1 <- "(?=(^...$|^..$))|(?=.*(?<=Airport))"
#splits only airports
regexp2 <- "(?=.*(?<=Airport))"
#splits only 3 letters
regexp3 <- "(?=(^...$))"
#now we perform a sweeping data clean
codes <- codes %>%
separate(col = 1,into = c("State", "StateAbbr"),
sep = regexp1,extra = "merge") %>%
separate(col = 2,into = c("StateAbbr", "Airport"),
sep = regexp2,extra="merge") %>%
separate(col = 2, into = c("StateAbbr","AirportCode"),
sep = regexp3, extra = "merge") %>%
mutate(
StateAbbr = lead(StateAbbr),
AirportCode = lead(AirportCode, n = 3),
Airport = lead(Airport, n = 2)
) %>%
drop_na() %>%
na_if("") %>%
fill(State,StateAbbr)
Expected 2 pieces. Missing pieces filled with `NA` in 55 rows [1, 13, 21, 31, 39, 65, 77, 83, 89, 117, 125, 137, 141, 151, 161, 167, 171, 177, 185, 189, ...].Expected 2 pieces. Missing pieces filled with `NA` in 237 rows [2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, ...].Expected 2 pieces. Missing pieces filled with `NA` in 238 rows [2, 3, 5, 7, 9, 11, 14, 15, 17, 19, 22, 23, 25, 27, 29, 32, 33, 35, 37, 40, ...].
Now look at that gorgeous dataframe
show(codes)
The next step is to join our custom dataframe to the nycflights dataframe.
We run into an issue however… the nycflights isn’t necessarily tidy. We’d ideally like to join on origin or destination, but which one? The problem here is that we can’t because it is not tidy. We need to adjust those columns such that we have the airport code in one column and the “type” (either origin or departure in the other).
tidy.flights <- flights %>%
pivot_longer(cols = c(origin,dest), names_to = "airport_code_type",
values_to = "AirportCode")
tidy.flights <- tidy.flights %>%
left_join(codes, by = "AirportCode") %>%
drop_na()
Advanced Web Scraping
Not done yet. To see flight data we need the latitude and longitude of the airports. Let’s scrape this site: www.dices.net/movil/airports/airports-United_States-US-1.html
The issue here is that our data is spread across multiple pages or URLs.
coords.html <- lapply(
paste0(
"http://www.dices.net/movil/airports/airports-United_States-US-",1:103,".html"
),function(url){
url %>%
read_html() %>%
html_nodes("b") %>%
html_text()
})
#returns only the the 5th-84th elements as the others are not needed
coords.html <- lapply(coords.html, function(x){x[5:84]})
coords.vec <- unlist(coords.html) #unlist into vector
AirportCodes <- str_extract(coords.vec, "^...$") #get 3 letter codes
latlong <- str_extract(coords.vec, "(\\d.*)|(-\\d.*)") #get the digits
for(i in seq(3,(length(latlong)-1),4)){
latlong[i] <- paste0(latlong[i],",",latlong[i+1])
}
latlong <- str_extract(latlong,".*,.*")
coords <- data.frame(AirportCode = AirportCodes, latlong = latlong) %>%
mutate(AirportCode = lead(AirportCode), latlong = lead(latlong, n = 2)) %>%
drop_na() %>%
separate(latlong, c("lat","long"), sep = ",") %>% #now we can split on the ","
mutate(lat = as.numeric(lat),
long = as.numeric(long)) #convert from string
Now we can finally join in Airport Code
final <- coords %>% left_join(tidy.flights, by = "AirportCode") %>%
drop_na()
Finally! Look at that dataframe
show(head(final))
Visualizing the NYC flight destinations
We filter by "dest and see all the flight destinations from NY
library(leaflet)
dests <- final %>% filter(airport_code_type == "dest")
leaflet(dests) %>% addTiles() %>% addMarkers(clusterOptions = markerClusterOptions())
Assuming "long" and "lat" are longitude and latitude, respectively
LS0tDQp0aXRsZTogIlVzaW5nIExlYWZsZXQgdG8gTG9vayBhdCBOWUMgRmxpZ2h0cyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQpgYGB7ciBnbG9iYWwtb3B0aW9ucywgaW5jbHVkZSA9IEZBTFNFfQ0KDQprbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NCwgZHBpPSA4MDAsDQogICAgICAgICAgICAgICAgICAgICAgZWNobz1UUlVFLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFKQ0KYGBgDQojIEludHJvZHVjdGlvbg0KDQpUaGUgTllDIGZsaWdodHMgZGF0YXNldCBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCBmbGlnaHRzIGNvbWluZyBpbiBhbmQgb3V0DQpvZiBOWUMuIFdlIGNhbiBpbnNwZWN0IHRoZSBmaXJzdCBmZXcgZWxlbWVudHMgaGVyZS4gDQoNCmBgYHtyfQ0KbGlicmFyeShueWNmbGlnaHRzMTMpOyBsaWJyYXJ5KHJ2ZXN0KTsgbGlicmFyeSh0aWR5dmVyc2UpDQpoZWFkKGZsaWdodHMpDQpgYGANCg0KTGV0J3Mgc2F5IHdlIHdvdWxkIGxpa2UgdG8gdmlzdWFsaXplIHRoaXMgZGF0YXNldCB1c2luZyBsZWFmbGV0LiBUaGUgcHJvYmxlbQ0KaXMgdGhhdCB3ZSBoYXZlIGEgYnVuY2ggb2YgYWlycG9ydCBjb2RlcyBidXQgd2UgZG9uJ3Qga25vdyB3aGF0IGNpdHkgdGhleSdyZSBpbg0KSXQgd291bGQgYmUgbmljZSBpZiB3ZSBoYWQgaXQgbWFwcGVkIG91dCBmb3IgdXMgKGUuZy4gTEFYID0gTG9zIEFuZ2VsZXMpIGJ1dA0KdW5mb3J0dW5hdGVseSB0aGF0IGlzIG5vdCB0aGUgY2FzZS4NCg0KR29vZCBuZXdzIGlzIHRoYXQgaW5mb3JtYXRpb24gaXMgZWFzaWx5IGF2YWlsYWJsZSBvbiB0aGUgd2ViISBPbmUgZG9lc24ndCBoYXZlDQp0byBsb29rIHRvIGZhciB0byBzZWUgYSB0YWJsZSBvZiBhaXJwb3J0IGNvZGVzIGFuZCB0aGUgY2l0eSB0aGV5IGJlbG9uZyB0by4NCg0KTG9va2luZyBhdCB0aGUgc2l0ZSBodHRwOi8vd3d3Lmxlb25hcmRzZ3VpZGUuY29tL3VzLWFpcnBvcnQtY29kZXMuc2h0bWwgd2Ugc2VlDQphIG5pY2UgdGFibGUgYWxyZWFkeSBtYWRlIGZvciB1cy4NCg0KKipCdXQgaG93IGRvIEkgZ2V0IHRoYXQgZGF0YSB0aG91Z2g/KioNCg0KV2UgY2FuIGVtcGxveSBSJ3MgYXdlc29tZSAicnZlc3QiIHBhY2thZ2UgZm9yIHdlYiBzY3JhcGluZyB3aGF0ZXZlciB3ZSdkIGxpa2UuDQoNCldlYnNjcmFwaW5nIGlzIHJlbGF0aXZlbHkgaW50dWl0aXZlLiBUaGVyZSBvbmx5IGEgZmV3IGZ1bmN0aW9ucyB3ZSBuZWVkOg0KDQotIHJlYWRfaHRtbCgpIHNjcmFwZXMgdGhlIHByb2dyYW1taW5nIGZyb20gdGhlIHNpdGUuDQoNCi0gcmVhZF9ub2RlcygpIHJldHVybnMgdGhlIGRhdGEgY29udGFpbmVkIGluIGNlcnRhaW4gSFRNTCAibm9kZXMiDQoNCi0gaHRtbF90ZXh0KCkgY29udmVydHMgdGhpcyB0byB0ZXh0DQoNCiMgV2ViU2NyYXBlIHRoZSBBaXJwb3J0IGNvZGVzDQoNCkxldCdzIHNlZSB3aGF0IGl0IGxvb2tzIGxpa2UgaW4gYWN0aW9uDQpgYGB7cn0NCg0KaHRtbC5zY3JhcGUgPC0gcmVhZF9odG1sKCJodHRwOi8vd3d3Lmxlb25hcmRzZ3VpZGUuY29tL3VzLWFpcnBvcnQtY29kZXMuc2h0bWwiKQ0KYGBgDQoNCk5vdyB0aGUgdHJpY2t5IHBhcnQgaXMgc2VlaW5nIHdoaWNoIG5vZGVzIGNvbnRhaW4gb3VyIGRhdGEuIEhlcmUgaXQgd291bGQgYmUNCndpc2UgdXNlIGNocm9tZSBkZXYgdG9vbHMgdG8gc2VlIHdoaWNoIG5vZGUgY29udGFpbnMgdGhlIGluZm8uIEhlcmUgaXQgbG9va3MNCmxpa2UgdGhlIDx0ZD4gbm9kZSBjb250YWlucyB0aGUgZGF0YS4NCg0KYGBge3J9DQpjb2RlcyA8LSBodG1sLnNjcmFwZSAlPiUgaHRtbF9ub2RlcygidGQiKSAlPiUgaHRtbF90ZXh0KCkNCmBgYA0KDQpMZXQncyB0YWtlIGEgbW9tZW50IHRvIHJlY2FwOg0KDQotIFdlIHJlYWQgaW4gdGhlIGVudGlyZSBodG1sIGNvZGUgY29udGFpbmVkIGluIHRoZSBzaXRlDQotIFdlIGZvdW5kIHRoZSBub2RlIHRoYXQgY29udGFpbnMgb3VyIGluZm9ybWF0aW9uDQotIFdlIGdvdCB0aGUgZGF0YSB3aXRoIGh0bWxfbm9kZXMoKQ0KLSBXZSBjb252ZXJ0ZWQgaW50byB0ZXh0IHdpdGggaHRtbF90ZXh0KCkNCg0KQnV0IGl0IGlzIHN0aWxsIG5vdCBpbiBhIGZvcm1hdCB3ZSB3b3VsZCBsaWtlLiBJdCBzaG91bGQgYmUgYSBkYXRhZnJhbWUgd2l0aA0Kc3RhdGUsIGNpdHksIGFuZCBhaXJwb3J0IGNvZGUgYXMgY29sdW1ucyBpbnN0ZWFkIG9mIGEgbG9uZyB2ZWN0b3IuIE5vdyBjb21lcyANCnRoZSBwb3dlciBvZiB0aGUgdGlkeXZlcnNlLg0KDQpgYGB7cn0NCmNvZGVzIDwtIGFzLmRhdGEuZnJhbWUoY29kZXMpDQpzaG93KGhlYWQoY29kZXMpKQ0KYGBgDQoNCkxvb2tpbmcgYXQgdGhlIGFib3ZlLCBhdCBmaXJzdCBnbGFuY2UgaXQgd291bGQgc2VlbSBkaWZmaWN1bHQgdG8gZ2V0IHRoaXMgaW50bw0KdGhlIGRhdGEgZnJhbWUgd2Ugd2FudC4gV2UgYWxzbyBjYW4gc2VlIG9uZSBvYnNlcnZhdGlvbiB0aGF0IHdlIG5lZWQgdG8gZml4Li4uDQoNCllvdSBjYW4gc2VlIHRoYXQgc29tZSBhaXJwb3J0cyBoYXZlIHRoZSB3b3JkICJBaXJwb3J0IiBhdHRhY2hlZCB0byB0aGVpcg0KbmFtZSwgd2hpbGUgb3RoZXJzIGRvIG5vdC4gVGhpcyBtYWtlcyBpdCBoYXJkIHRvIGRpc3Rpbmd1aXNoIHdoaWNoIGlzIGFuDQphaXJwb3J0IGFuZCB3aGF0IGlzIG5vdC4gV2Uga25vdyB0aGF0IGFueSAzIGxldHRlciBhaXJwb3J0IGNvZGUgbXVzdCBoYXZlIGENCnByZWNlZGluZyBhaXJwb3J0IGluIHRoZSBlbGVtZW50IGFib3ZlIGl0LiBMZXQncyB3cml0ZSBjb2RlIHRvIHdyaXRlIHRoZSB3b3JkDQoiQWlycG9ydCIgdG8gYW55IGVsZW1lbnQgdGhhdCBuZWVkcyBpdC4NCg0KKipFeGFtcGxlOioqDQoNCioqV2Ugc2VlIHRoYXQgTU9CJ3MgYWlycG9ydCBuYW1lIGlzICJNb2JpbGUiIGFuZCBzaG91bGQgYmUgIk1vYmlsZSBBaXJwb3J0IioqDQoNCmBgYHtyfQ0KI2xvb3AgdGhyb3VnaCBkYXRhZnJhbWUNCmZvcihpIGluIDE6bnJvdyhjb2Rlcykpew0KDQogICNmaW5kIDMgbGV0dGVyIGFpcnBvcnQgY29kZXMNCiAgaWYobmNoYXIoY29kZXNbW2ksMV1dKSA9PSAzKXsNCiAgICANCiAgICAjY2hlY2sgaWYgdGhlIHN0cmluZyBhYm92ZSBpdCBjb250YWlucyB0aGUgd29yZCAiQWlycG9ydCINCiAgICBpZighc3RyX2RldGVjdChjb2Rlc1tbaS0xLDFdXSwgIkFpcnBvcnQiKSl7DQogICAgICANCiAgICAgICNpZiBpdCBkb2Vzbid0IHRoZW4gd3JpdGUgdGhlIHdvcmQgIkFpcnBvcnQiIGF0IHRoZSBlbmQNCiAgICAgIGNvZGVzW1tpLTEsIDFdXSA8LSBwYXN0ZShjb2Rlc1tbaS0xLDFdXSwgIkFpcnBvcnQiLCBzZXAgPSAiICIpDQogICAgfQ0KICB9DQp9DQoNCmBgYA0KDQpOb3cgd2UgY2FuIHRha2UgZnVsbCBhZHZhbnRhZ2Ugb2YgUidzIHNlcGFyYXRlIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCiN1c2UgUidzIHNlcGFyYXRlIGZ1bmN0aW9uIGFzIHdlIG5lZWQgdG8gc2VwYXJhdGUgMyB0aW1lcw0KDQojZmlyc3QgcmVnZXggc3BsaXRzIGFueXRoaW5nIHRoYXRzIDIgbGV0dGVycyBvciAzIGxldHRlcnMgb3IgY29udGFpbnMgIkFpcnBvcnQiDQojbm93IHlvdSBzZWUgd2h5IHdlIG5lZWRlZCB0byBhZGQgdGhlIHdvcmQgQWlycG9ydCB0byBhIGZldyBlbGVtZW50cw0KcmVnZXhwMSA8LSAiKD89KF4uLi4kfF4uLiQpKXwoPz0uKig/PD1BaXJwb3J0KSkiDQoNCiNzcGxpdHMgb25seSBhaXJwb3J0cw0KcmVnZXhwMiA8LSAiKD89LiooPzw9QWlycG9ydCkpIg0KDQojc3BsaXRzIG9ubHkgMyBsZXR0ZXJzDQpyZWdleHAzIDwtICIoPz0oXi4uLiQpKSINCg0KI25vdyB3ZSBwZXJmb3JtIGEgc3dlZXBpbmcgZGF0YSBjbGVhbg0KY29kZXMgPC0gY29kZXMgJT4lDQogIHNlcGFyYXRlKGNvbCA9IDEsaW50byA9IGMoIlN0YXRlIiwgIlN0YXRlQWJiciIpLA0KICAgICAgICAgICBzZXAgPSByZWdleHAxLGV4dHJhID0gIm1lcmdlIikgJT4lIA0KICBzZXBhcmF0ZShjb2wgPSAyLGludG8gPSBjKCJTdGF0ZUFiYnIiLCAiQWlycG9ydCIpLA0KICAgICAgICAgICBzZXAgPSByZWdleHAyLGV4dHJhPSJtZXJnZSIpICU+JSANCiAgc2VwYXJhdGUoY29sID0gMiwgaW50byA9IGMoIlN0YXRlQWJiciIsIkFpcnBvcnRDb2RlIiksIA0KICAgICAgICAgICBzZXAgPSByZWdleHAzLCBleHRyYSA9ICJtZXJnZSIpICU+JSANCiAgbXV0YXRlKA0KICAgIFN0YXRlQWJiciA9IGxlYWQoU3RhdGVBYmJyKSwgDQogICAgQWlycG9ydENvZGUgPSBsZWFkKEFpcnBvcnRDb2RlLCBuID0gMyksDQogICAgQWlycG9ydCA9IGxlYWQoQWlycG9ydCwgbiA9IDIpDQogICAgKSAlPiUgDQogIGRyb3BfbmEoKSAlPiUgDQogIG5hX2lmKCIiKSAlPiUgDQogIGZpbGwoU3RhdGUsU3RhdGVBYmJyKQ0KDQpgYGANCg0KTm93IGxvb2sgYXQgdGhhdCBnb3JnZW91cyBkYXRhZnJhbWUNCmBgYHtyfQ0Kc2hvdyhjb2RlcykNCmBgYA0KDQpUaGUgbmV4dCBzdGVwIGlzIHRvIGpvaW4gb3VyIGN1c3RvbSBkYXRhZnJhbWUgdG8gdGhlIG55Y2ZsaWdodHMgZGF0YWZyYW1lLg0KDQpXZSBydW4gaW50byBhbiBpc3N1ZSBob3dldmVyLi4uIHRoZSBueWNmbGlnaHRzIGlzbid0IG5lY2Vzc2FyaWx5IHRpZHkuIFdlJ2QNCmlkZWFsbHkgbGlrZSB0byBqb2luIG9uIG9yaWdpbiBvciBkZXN0aW5hdGlvbiwgYnV0IHdoaWNoIG9uZT8gVGhlIHByb2JsZW0gaGVyZQ0KaXMgdGhhdCB3ZSBjYW4ndCBiZWNhdXNlIGl0IGlzICoqbm90IHRpZHkqKi4gV2UgbmVlZCB0byBhZGp1c3QgdGhvc2UgY29sdW1ucw0Kc3VjaCB0aGF0IHdlIGhhdmUgdGhlIGFpcnBvcnQgY29kZSBpbiBvbmUgY29sdW1uIGFuZCB0aGUgInR5cGUiIChlaXRoZXIgb3JpZ2luDQpvciBkZXBhcnR1cmUgaW4gdGhlIG90aGVyKS4NCg0KYGBge3J9DQp0aWR5LmZsaWdodHMgPC0gZmxpZ2h0cyAlPiUgDQogIHBpdm90X2xvbmdlcihjb2xzID0gYyhvcmlnaW4sZGVzdCksIG5hbWVzX3RvID0gImFpcnBvcnRfY29kZV90eXBlIiwNCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJBaXJwb3J0Q29kZSIpDQoNCnRpZHkuZmxpZ2h0cyA8LSB0aWR5LmZsaWdodHMgJT4lIA0KICBsZWZ0X2pvaW4oY29kZXMsIGJ5ID0gIkFpcnBvcnRDb2RlIikgJT4lIA0KICBkcm9wX25hKCkNCmBgYA0KDQojIEFkdmFuY2VkIFdlYiBTY3JhcGluZw0KTm90IGRvbmUgeWV0LiBUbyBzZWUgZmxpZ2h0IGRhdGEgd2UgbmVlZCB0aGUgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBvZiB0aGUNCmFpcnBvcnRzLiBMZXQncyBzY3JhcGUgdGhpcyBzaXRlOg0Kd3d3LmRpY2VzLm5ldC9tb3ZpbC9haXJwb3J0cy9haXJwb3J0cy1Vbml0ZWRfU3RhdGVzLVVTLTEuaHRtbA0KDQpUaGUgaXNzdWUgaGVyZSBpcyB0aGF0IG91ciBkYXRhIGlzIHNwcmVhZCBhY3Jvc3MgKiptdWx0aXBsZSBwYWdlcyoqIG9yIFVSTHMuDQpgYGB7cn0NCg0KY29vcmRzLmh0bWwgPC0gbGFwcGx5KA0KcGFzdGUwKA0KICJodHRwOi8vd3d3LmRpY2VzLm5ldC9tb3ZpbC9haXJwb3J0cy9haXJwb3J0cy1Vbml0ZWRfU3RhdGVzLVVTLSIsMToxMDMsIi5odG1sIg0KKSxmdW5jdGlvbih1cmwpew0KICB1cmwgJT4lDQogICAgcmVhZF9odG1sKCkgJT4lDQogICAgaHRtbF9ub2RlcygiYiIpICU+JQ0KICAgIGh0bWxfdGV4dCgpDQp9KQ0KI3JldHVybnMgb25seSB0aGUgdGhlIDV0aC04NHRoIGVsZW1lbnRzIGFzIHRoZSBvdGhlcnMgYXJlIG5vdCBuZWVkZWQNCmNvb3Jkcy5odG1sIDwtIGxhcHBseShjb29yZHMuaHRtbCwgZnVuY3Rpb24oeCl7eFs1Ojg0XX0pDQoNCmNvb3Jkcy52ZWMgPC0gdW5saXN0KGNvb3Jkcy5odG1sKSAjdW5saXN0IGludG8gdmVjdG9yDQpBaXJwb3J0Q29kZXMgPC0gc3RyX2V4dHJhY3QoY29vcmRzLnZlYywgIl4uLi4kIikgI2dldCAzIGxldHRlciBjb2Rlcw0KbGF0bG9uZyA8LSBzdHJfZXh0cmFjdChjb29yZHMudmVjLCAiKFxcZC4qKXwoLVxcZC4qKSIpICNnZXQgdGhlIGRpZ2l0cw0KDQpmb3IoaSBpbiBzZXEoMywobGVuZ3RoKGxhdGxvbmcpLTEpLDQpKXsNCiAgbGF0bG9uZ1tpXSA8LSBwYXN0ZTAobGF0bG9uZ1tpXSwiLCIsbGF0bG9uZ1tpKzFdKQ0KfQ0KDQpsYXRsb25nIDwtIHN0cl9leHRyYWN0KGxhdGxvbmcsIi4qLC4qIikNCg0KY29vcmRzIDwtIGRhdGEuZnJhbWUoQWlycG9ydENvZGUgPSBBaXJwb3J0Q29kZXMsIGxhdGxvbmcgPSBsYXRsb25nKSAlPiUgDQogIG11dGF0ZShBaXJwb3J0Q29kZSA9IGxlYWQoQWlycG9ydENvZGUpLCBsYXRsb25nID0gbGVhZChsYXRsb25nLCBuID0gMikpICU+JSANCiAgZHJvcF9uYSgpICU+JSANCiAgc2VwYXJhdGUobGF0bG9uZywgYygibGF0IiwibG9uZyIpLCBzZXAgPSAiLCIpICU+JSAjbm93IHdlIGNhbiBzcGxpdCBvbiB0aGUgIiwiDQogIG11dGF0ZShsYXQgPSBhcy5udW1lcmljKGxhdCksDQogICAgICAgICBsb25nID0gYXMubnVtZXJpYyhsb25nKSkgI2NvbnZlcnQgZnJvbSBzdHJpbmcNCmBgYA0KDQpOb3cgd2UgY2FuIGZpbmFsbHkgam9pbiBpbiBBaXJwb3J0IENvZGUNCmBgYHtyfQ0KZmluYWwgPC0gY29vcmRzICU+JSBsZWZ0X2pvaW4odGlkeS5mbGlnaHRzLCBieSA9ICJBaXJwb3J0Q29kZSIpICU+JQ0KICBkcm9wX25hKCkgDQpgYGANCg0KRmluYWxseSEgTG9vayBhdCB0aGF0IGRhdGFmcmFtZQ0KYGBge3J9DQpzaG93KGhlYWQoZmluYWwpKQ0KYGBgDQoNCiMgVmlzdWFsaXppbmcgdGhlIE5ZQyBmbGlnaHQgZGVzdGluYXRpb25zDQoNCldlIGZpbHRlciBieSAiZGVzdCBhbmQgc2VlIGFsbCB0aGUgZmxpZ2h0IGRlc3RpbmF0aW9ucyBmcm9tIE5ZDQpgYGB7cn0NCmxpYnJhcnkobGVhZmxldCkNCg0KZGVzdHMgPC0gZmluYWwgJT4lIGZpbHRlcihhaXJwb3J0X2NvZGVfdHlwZSA9PSAiZGVzdCIpDQoNCmxlYWZsZXQoZGVzdHMpICU+JSBhZGRUaWxlcygpICU+JSANCiAgYWRkTWFya2VycyhjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkpDQoNCmBgYA0KDQo=
|